#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.ComponentModel;
#endregion

namespace Xfinity.Engine.Plugins
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
    public partial class ParticleSystem : DrawableGameComponent, IComponent
    {

        /// <summary>
        /// Represents an individual particle.
        /// </summary>
        public struct Particle
        {
            #region Constructor
            public Particle(Vector3 position, Vector3 velocity, Vector3 acceleration, float mass, Color color)
            {
                this.position = position;
                this.velocity = velocity;
                this.acceleration = acceleration;
                this.mass = mass;
                this.color = color;
                size = new Vector3(1);
                age = 0;
            }
            #endregion
            #region Position
            private Vector3 position;

            public Vector3 Position
            {
                get { return position; }
                set { position = value; }
            }
            #endregion
            #region Velocity
            private Vector3 velocity;

            public Vector3 Velocity
            {
                get { return velocity; }
                set { velocity = value; }
            }
            #endregion
            #region Acceleration
            private Vector3 acceleration;

            public Vector3 Acceleration
            {
                get { return acceleration; }
                set { acceleration = value; }
            }

            #endregion
            #region Mass
            private float mass;

            public float Mass
            {
                get { return mass; }
                set { mass = value; }
            }

            #endregion
            #region Size
            private Vector3 size;

            public Vector3 Size
            {
                get { return size; }
                set { size = value; }
            }

            #endregion
            #region Age
            private double age;

            public double Age
            {
                get { return age; }
                set { age = value; }
            }

            #endregion
            #region Color
            private Microsoft.Xna.Framework.Graphics.Color color;

            public Microsoft.Xna.Framework.Graphics.Color Color
            {
                get { return color; }
                set { color = value; }
            }

            #endregion
        }


        #region Particle system properties
        #region Particles
        private List<Particle> particles;

        public List<Particle> Particles
        {
            get { return particles; }
        }
        #endregion
        #region Texture
        private Texture2D texture;

        public Texture2D Texture
        {
            get { return texture; }
            set { texture = value; }
        }
        private string texturePath = @".\Content\Textures\Particle";

        public string TexturePath
        {
            get { return texturePath; }
            set { texturePath = value; }
        }


        #endregion
        #region Alive
        private bool alive;

        public bool Alive
        {
            get { return alive; }
            set { alive = value; }
        }

        #endregion
        #region MaxParticles
        private int numberOfParticles;

        public int NumberOfParticles
        {
            get { return numberOfParticles; }
            set
            {
                numberOfParticles = value;
                particles.Capacity = value;
            }
        }

        #endregion
        #region EmitterPosition
        private Vector3 emitterPosition;

        public Vector3 EmitterPosition
        {
            get { return emitterPosition; }
            set { emitterPosition = value; }
        }

        #endregion
        #region MaxAge
        private double maxAge;

        public double MaxAge
        {
            get { return maxAge; }
            set { maxAge = value; }
        }
        #endregion
        #region Iterator
        public ParticleSystemIterator Iterator
        {
            get
            {
                return new ParticleSystemIterator(this);
            }
        }

        public class ParticleSystemIterator
        {
            readonly ParticleSystem outer;

            internal ParticleSystemIterator(ParticleSystem outer)
            {
                this.outer = outer;
            }

            public int Length { get { return outer.particles.Count; } }

            public Particle this[int index]
            {
                get
                {
                    return outer.particles[index];
                }
            }

            public System.Collections.Generic.IEnumerator<Particle> GetEnumerator()
            {
                for (int i = 0; i < this.Length; i++)
                {
                    yield return this[i];
                }
            }
        }
        #endregion
        #region Camera
        private Camera camera;

        public Camera Camera
        {
            get { return camera; }
            set { camera = value; }
        }
        #endregion
        #region Emitter
        private ParticleEmitter emitter;

        public ParticleEmitter Emitter
        {
            get { return emitter; }
            set { emitter = value; }
        }

        #endregion

        #endregion


        #region Constructor
        internal ParticleSystem(Game game, int numberOfParticles, Camera camera)
            : base(game)
        {
            emitter = new ParticleEmitter(EmitterOriginType.Point, EmitterDirectionType.Explosion, camera.Position);
            this.camera = camera;
            particles = new List<Particle>(numberOfParticles);
            content = new ContentManager(Game.Services);
        }
        #endregion


        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {

            base.Initialize();
        }


        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            //if there are no more live particles, disable the system
            if (particles.Count == 0)
            {
                Visible = false;
                Enabled = false;
                return;
            }

            float seconds = (float)gameTime.ElapsedRealTime.Seconds;

            particles.ForEach(delegate(Particle p)
            {
                //update the particle positions
                if (p.Age < maxAge)
                {
                    p.Velocity += p.Acceleration * seconds;
                    p.Position += p.Velocity * seconds;
                    p.Age += gameTime.ElapsedRealTime.TotalMilliseconds;
                }
                else //kill off the particle
                {
                    particles.Remove(p);
                }
            });


            base.Update(gameTime);
        }

        VertexDeclaration vertDeclaration;
        VertexBuffer vertBuffer;
        public override void Draw(GameTime gameTime)
        {
            //set up needed matrices and other data structures
            Matrix invViewMatrix = Matrix.Invert(camera.ViewMatrix);
            Vector3 camPos = camera.Position;
            Vector3 up = camera.Up;
            VertexPositionNormalTexture[] verts = new VertexPositionNormalTexture[4 * particles.Count];
            vertBuffer = new VertexBuffer(GraphicsDevice, VertexPositionNormalTexture.SizeInBytes * particles.Count, ResourceUsage.None, ResourceManagementMode.Automatic);
            //indexBuffer = new IndexBuffer(GraphicsDevice, sizeof(uint) * 6 * particles.Count, ResourceUsage.None, ResourceManagementMode.Automatic,sizeof(uint));
            uint[] indices = new uint[6 * particles.Count];

            //set up the effect
            effect.Parameters["WorldViewProj"].SetValue(camera.ViewProjectionMatrix);
            effect.Parameters["UserTexture"].SetValue(texture);
            effect.CurrentTechnique = effect.Techniques["TransformAndTexture"];

            //this hyar is draws each particle. it's messy.
            for (int i = 0; i < particles.Count; i++)
            {
                Vector3 camToParticle = particles[i].Position - camPos;
                camToParticle.Normalize();
                //generate the verts
                Vector3 leftDir = Vector3.Cross(camToParticle, up);
                Vector3 verticalOffset = up * (particles[i].Size.Y * 0.5f);
                Vector3 horizontalOffset = leftDir * (particles[i].Size.X * 0.5f);
                //Vector3 left = particles[i].Position - horizontalOffset;
                Vector3 top = particles[i].Position + verticalOffset;
                Vector3 bottom = particles[i].Position - verticalOffset;
                Vector3 normal = -camToParticle;
                Vector3 topLeft = top - horizontalOffset;
                Vector3 topRight = top + horizontalOffset;
                Vector3 bottomLeft = bottom - horizontalOffset;
                Vector3 bottomRight = bottom + horizontalOffset;

                //put the verts in the vertex buffer
                verts[i * 4] = new VertexPositionNormalTexture(topLeft, normal, Vector2.Zero);
                verts[(i * 4) + 1] = new VertexPositionNormalTexture(topRight, normal, Vector2.UnitX);
                verts[(i * 4) + 2] = new VertexPositionNormalTexture(bottomLeft, normal, Vector2.UnitY);
                verts[(i * 4) + 3] = new VertexPositionNormalTexture(bottomRight, normal, Vector2.One);

                //00:22:37] <@X-Tatic> you can easily render a quad with 2 trianglestrips and no index buffer
                //[00:22:57] <@X-Tatic> add them to a vertex buffer in the order, topleft, top right, bottom left, bottom right
                //ms-help://MS.VSExpressCC.v80/MS.VSIPCC.v80/MS.XNAFX.1033/XNA/Apply_Textures.htm
            }
            vertBuffer.SetData<VertexPositionNormalTexture>(verts);

            GraphicsDevice.VertexDeclaration = vertDeclaration;


            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                GraphicsDevice.Vertices[0].SetSource(
                    vertBuffer,
                    0,
                    VertexPositionTexture.SizeInBytes);

                GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2 * particles.Count);
                pass.End();
            }
            effect.End();


            base.Draw(gameTime);
        }
        ContentManager content;
        Effect effect;
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            if (loadAllContent)
            {
                content = new ContentManager(Game.Services);
                texture = content.Load<Texture2D>(texturePath);
                effect = content.Load<Effect>(@"Engine\Content\Effects\TexturedQuad");
                vertDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionNormalTexture.VertexElements);

            }
            base.LoadGraphicsContent(loadAllContent);

        }

        #region IComponent Members


        public ISite Site
        {
            get
            {
                return null;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        #endregion

    }
}